﻿using System;
using System.Windows;
using System.Windows.Input;
using System.Windows.Interop;
using System.Windows.Threading;
using AdTech.Common.WPF;
using AdTech.Snap.Interop;

namespace AdTech.Snap
{
    #pragma warning disable 1591
    public class OverlayViewModel : ViewModelBase, IDisposable
    {
        // Fields
        private readonly IConfiguration configModel;
        private readonly ISnapFactory snapFactory;
        private readonly IDropboxFactory dropboxIntegrator;
        private DispatcherTimer focusTimer;
        private IntPtr clientHandle;
        private Rect focusedWindowRect;
        private bool invokedByKey;
        private Rect croppedAreaToScreen;
        private Rect _CroppedArea;

        private DelegateCommand<RoutedEventArgs> _LoadedCommand;
        private DelegateCommand<MouseButtonEventArgs> _MouseLeftButtonDownCommand;
        private DelegateCommand<MouseButtonEventArgs> _MouseLeftButtonUpCommand;
        private DelegateCommand<MouseEventArgs> _MouseMoveCommand;
        private DelegateCommand<KeyEventArgs> _KeyDownCommand;
        private DelegateCommand<KeyEventArgs> _KeyUpCommand;
        private DelegateCommand<RoutedEventArgs> _UnloadedCommand;

        // Properties
        public IConfiguration Configuration { get { return configModel; } }
        /// <summary>
        /// Gets the full screen boundaries it which can be cropped.
        /// </summary>
        public Size ScreenBounds { get; private set; }
        /// <summary>
        /// Gets the last destination path where a screenshot has been dropped.
        /// </summary>
        public string DestinationPath { get; private set; }
        /// <summary>
        /// Gets the public Dropbox link to the recently created screenshot.
        /// </summary>
        public string PublicLink { get; private set; }
        /// <summary>
        /// Gets the result Rectangle.
        /// </summary>
        public Rect CroppedArea
        {
            get { return _CroppedArea; }
            private set { if (value == _CroppedArea) return; _CroppedArea = value; NotifyPropertyChanged(() => this.CroppedArea); }
        }
        #region Commands
        public ICommand LoadedCommand
        {
            get
            {
                if (_LoadedCommand == null)
                    _LoadedCommand = new DelegateCommand<RoutedEventArgs>(OnExecuteLoaded);
                return _LoadedCommand;
            }
        }
        public ICommand UnloadedCommand
        {
            get
            {
                if (_UnloadedCommand == null)
                    _UnloadedCommand = new DelegateCommand<RoutedEventArgs>(OnExecuteUnloaded);
                return _UnloadedCommand;
            }
        }
        public ICommand MouseLeftButtonDownCommand
        {
            get
            {
                if (_MouseLeftButtonDownCommand == null)
                    _MouseLeftButtonDownCommand = new DelegateCommand<MouseButtonEventArgs>(OnExecuteMouseLeftButtonDown);
                return _MouseLeftButtonDownCommand;
            }
        }
        public ICommand MouseLeftButtonUpCommand
        {
            get
            {
                if (_MouseLeftButtonUpCommand == null)
                    _MouseLeftButtonUpCommand = new DelegateCommand<MouseButtonEventArgs>(OnExecuteMouseLeftButtonUp);
                return _MouseLeftButtonUpCommand;
            }
        }
        public ICommand MouseMoveCommand
        {
            get
            {
                if (_MouseMoveCommand == null)
                    _MouseMoveCommand = new DelegateCommand<MouseEventArgs>(OnExecuteMouseMove);
                return _MouseMoveCommand;
            }
        }
        public ICommand KeyDownCommand
        {
            get
            {
                if (_KeyDownCommand == null)
                    _KeyDownCommand = new DelegateCommand<KeyEventArgs>(OnExecuteKeyDown);
                return _KeyDownCommand;
            }
        }
        public ICommand KeyUpCommand
        {
            get
            {
                if (_KeyUpCommand == null)
                    _KeyUpCommand = new DelegateCommand<KeyEventArgs>(OnExecuteKeyUp);
                return _KeyUpCommand;
            }
        }
        #endregion

        public OverlayViewModel(IConfiguration configModel, ISnapFactory snapFactory, IDropboxFactory dropboxIntegrator)
        {
            this.configModel = configModel;
            this.snapFactory = snapFactory;
            this.dropboxIntegrator = dropboxIntegrator;
            this.focusTimer = new DispatcherTimer { Interval = TimeSpan.FromSeconds(1) };
            this.focusedWindowRect = WindowInteropFactory.GetFocusedWindowRect();
        }

        #region Commands
        protected virtual void OnExecuteLoaded(RoutedEventArgs e)
        {
            Window sender = e.Source as Window;
            if (sender == null)
                return;

            this.Initialize(new WindowInteropHelper(sender).Handle, new Size(sender.Width, sender.Height));
        }

        protected virtual void OnExecuteUnloaded(RoutedEventArgs e)
        {
            this.Dispose();
        }

        protected virtual void OnExecuteMouseLeftButtonDown(MouseButtonEventArgs e)
        {
            this.snapFactory.StartDrawing(e.GetPosition(null));
        }

        protected virtual void OnExecuteMouseLeftButtonUp(MouseButtonEventArgs e)
        {
            this.snapFactory.FinishDrawing();
            this.croppedAreaToScreen = this.snapFactory.GetDrawedRectangle(this.clientHandle);
            this.CreateScreenshot(this.croppedAreaToScreen);
        }

        protected virtual void OnExecuteMouseMove(MouseEventArgs e)
        {
            this.snapFactory.Draw(e.GetPosition(null));
            if (this.snapFactory.IsDrawing)
                Dispatcher.CurrentDispatcher.Invoke((Action)(() => this.CroppedArea = this.snapFactory.GetDrawedRectangle()), DispatcherPriority.Render);
        }

        protected virtual void OnExecuteKeyDown(KeyEventArgs e)
        {
            FrameworkElement sender = e.Source as FrameworkElement;
            if (sender == null)
                return;

            if (e.Key == Key.Enter || e.Key == Key.Return)
            {
                if (Keyboard.Modifiers == ModifierKeys.Shift)
                {
                    this.CroppedArea = WindowInteropFactory.ScreenToClient(this.focusedWindowRect, this.clientHandle);
                    this.croppedAreaToScreen = this.focusedWindowRect;
                }
                else
                {
                    this.CroppedArea = MonitorInteropHelper.GetTotalScreenBoundaries(true);
                    this.croppedAreaToScreen = MonitorInteropHelper.GetTotalScreenBoundaries();
                }

                this.invokedByKey = true;
            }
        }

        protected virtual void OnExecuteKeyUp(KeyEventArgs e)
        {
            if (this.invokedByKey)
            {
                this.CreateScreenshot(this.croppedAreaToScreen);
                this.invokedByKey = false;
            }
        }
        #endregion

        protected virtual void Initialize(IntPtr overlayHandle, Size overlayBoundaries)
        {
            // Initialize Overlay with full border
            this.clientHandle = overlayHandle;
            this.ScreenBounds = overlayBoundaries;
            this.CroppedArea = Rect.Empty;
#if !DEBUG
            this.focusTimer.Tick += (t_sender, t_e) => WindowInteropFactory.ForceFocus(this.clientHandle);
            this.focusTimer.Start();
#endif
        }

        protected virtual void CreateScreenshot(Rect selection)
        {
            if (selection.IsEmptyOrZero())
                return;

            System.Threading.Thread.Sleep(100);
            try
            {
                this.DestinationPath = this.snapFactory.SelectDestination();
                this.snapFactory.CaptureScreen(this.DestinationPath, selection.TopLeft, selection.Size);
                if (this.Configuration.UseDropbox)
                {
                    Uri publicURL = this.dropboxIntegrator.GenerateDropboxPublicLink(this.DestinationPath);
                    this.PublicLink = publicURL.AbsoluteUri;
                    Clipboard.SetText(this.PublicLink);
                }
                this.DialogResult = true;
            }
            catch (Exception ex)
            {
                Log.Default.Log(ex);
                throw;
            }
            finally
            {
                this.CloseCommand.Execute(null);
            }
        }

        #region IDisposable Members
        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        // The bulk of the clean-up code is implemented in Dispose(bool)
        /// <summary>
        /// <remarks>http://msdn.microsoft.com/en-us/library/ms244737.aspx</remarks>
        /// </summary>
        protected virtual void Dispose(bool disposing)
        {
            if (disposing)
            {
                if (this.focusTimer != null && this.focusTimer.IsEnabled)
                    this.focusTimer.Stop();
            }
        }
        #endregion
    }
    #pragma warning restore 1591
}
